using System;
using System.Text;

namespace csammisrun.OscarLib.Utility
{
  /// <summary>
  /// The Marshal class contains utility functions
  /// to aid in marshalling and unmarshalling data into OSCAR-compatable
  /// format.
  /// </summary>
  /// <remarks>
  /// <para>
  /// The BitConverter class was not used to aid in byte-array-to-data-type
  /// functions due to the endian-ness of the data.
  /// As an example, consider the following byte array:
  /// <code>byte[] buffer = new byte[] {0x00,0x01,0x02,0x03};</code>
  /// When asked to create a ushort from the first two bytes in the array,
  /// BitConverter will return 128 (0x0100) as opposed to 1 (0x0001). The OSCAR
  /// protocol expects byte buffers to be read left-to-right, in network order
  /// format.
  /// </para>
  /// <para>However, ICQ functions don't use network byte order, they use little-endian.
  /// Those functions are handled by the <see cref="MarshalLE"/> class.</para>
  /// </remarks>
  internal static class Marshal
  {
    /// <summary>
    /// Marshals a FLAP header into byte array
    /// </summary>
    /// <param name="header">A populated FLAP header</param>
    /// <param name="buffer">A byte array</param>
    /// <param name="index">The index at which to insert into the array</param>
    public static void InsertFLAPHeader(FLAPHeader header, byte[] buffer, ref int index)
    {
      buffer[index++] = FLAPHeader.IDByte;
      buffer[index++] = header.Channel;
      buffer[index++] = (byte)((header.DatagramSequenceNumber & 0xFF00) >> 8);
      buffer[index++] = (byte)(header.DatagramSequenceNumber & 0x00FF);
      buffer[index++] = (byte)((header.DataSize & 0xFF00) >> 8);
      buffer[index++] = (byte)(header.DataSize & 0x00FF);
    }

    /// <summary>
    /// Marshals a FLAP header into byte array
    /// </summary>
    /// <param name="header">A populated FLAP header</param>
    /// <param name="buffer">A byte array</param>
    /// <param name="index">The index at which to insert into the array</param>
    /// <remarks>This method does not change the index parameter.</remarks>
    public static void InsertFLAPHeader(FLAPHeader header, byte[] buffer, int index)
    {
      Marshal.InsertFLAPHeader(header, buffer, ref index);
    }

    /// <summary>
    /// Unmarshals a byte array into a FLAP header
    /// </summary>
    /// <param name="buffer">A byte array</param>
    /// <returns>A populated FLAP header</returns>
    /// <remarks>
    /// This method always assumes that the FLAP header is at the beginning of
    /// the buffer. The FLAP header is always the first six bytes of a communication
    /// frame coming from the server, and it is read into a six-byte buffer by
    /// itself at the beginning of a socket read operation.
    /// </remarks>
    public static FLAPHeader GetFLAPHeader(byte[] buffer)
    {
      FLAPHeader retval = new FLAPHeader();
      if (buffer != null)
      {
        retval.Channel = buffer[1];
        retval.DatagramSequenceNumber = (ushort)((buffer[2] << 8) | buffer[3]);
        retval.DataSize = (ushort)((buffer[4] << 8) | buffer[5]);
      }
      return retval;
    }

    /// <summary>
    /// Unmarshals a byte array into a SNAC header
    /// </summary>
    /// <param name="buffer">A byte array</param>
    /// <returns>A populated SNAC header</returns>
    /// <remarks>
    /// This method always assumes that the SNAC header is at the beginning of
    /// the buffer, because if a FLAP header on channel 0x02 is received,
    /// the first 10 bytes read following the FLAP header will always be the
    /// SNAC header.
    /// </remarks>
    public static SNACHeader GetSNACHeader(byte[] buffer)
    {
      SNACHeader retval;
      retval.FamilyServiceID = (ushort)((buffer[0] << 8) | buffer[1]);
      retval.FamilySubtypeID = (ushort)((buffer[2] << 8) | buffer[3]);
      retval.Flags = (ushort)((buffer[4] << 8) | buffer[5]);
      retval.RequestID =
        (uint)((buffer[6] << 24) | (buffer[7] << 16) | (buffer[8] << 8) | buffer[9]);
      return retval;
    }

    /// <summary>
    /// Marshals a SNAC header into a byte array
    /// </summary>
    /// <param name="header">A populated SNAC header</param>
    /// <param name="buffer">A byte array</param>
    /// <param name="index">The index at which to insert into the array</param>
    public static void InsertSNACHeader(SNACHeader header, byte[] buffer, ref int index)
    {
      buffer[index++] = (byte)((header.FamilyServiceID & 0xFF00) >> 8);
      buffer[index++] = (byte)(header.FamilyServiceID & 0x00FF);
      buffer[index++] = (byte)((header.FamilySubtypeID & 0xFF00) >> 8);
      buffer[index++] = (byte)(header.FamilySubtypeID & 0x00FF);
      buffer[index++] = (byte)((header.Flags & 0xFF00) >> 8);
      buffer[index++] = (byte)(header.Flags & 0x00FF);
      buffer[index++] = (byte)((header.RequestID & 0xFF000000) >> 24);
      buffer[index++] = (byte)((header.RequestID & 0x00FF0000) >> 16);
      buffer[index++] = (byte)((header.RequestID & 0x0000FF00) >> 8);
      buffer[index++] = (byte)(header.RequestID & 0x000000FF);
    }

    /// <summary>
    /// Unmarshals a byte array into a TLV structure
    /// </summary>
    /// <param name="buffer">A byte array</param>
    /// <param name="index">The position of the TLV</param>
    /// <returns>A populated TLV structure</returns>
    public static TLV GetTLV(byte[] buffer, ref int index)
    {
      TLV retval = new TLV();

      retval.TypeNumber = Marshal.ByteArrayToUshort(buffer, ref index);
      retval.DataSize = Marshal.ByteArrayToUshort(buffer, ref index);

      retval.Data = new byte[retval.DataSize];
      for (int i = 0; i < retval.DataSize; i++, index++)
        retval.Data[i] = buffer[index];
      return retval;
    }

    /// <summary>
    /// Marshals a list of TLVs into a byte array
    /// </summary>
    /// <param name="tlvs">A list of populated TLV structures</param>
    /// <param name="buffer">A byte array</param>
    /// <param name="index">The index at which to insert into the array</param>
    public static void InsertTLVs(TLV[] tlvs, byte[] buffer, ref int index)
    {
      if (tlvs == null) return;

      foreach (TLV tlv in tlvs)
      {
        buffer[index++] = (byte)((tlv.TypeNumber & 0xFF00) >> 8);
        buffer[index++] = (byte)(tlv.TypeNumber & 0x00FF);
        buffer[index++] = (byte)((tlv.DataSize & 0xFF00) >> 8);
        buffer[index++] = (byte)(tlv.DataSize & 0x00FF);
        if (tlv.Data != null) Marshal.CopyArray(tlv.Data, buffer, 0, index);
        index += tlv.DataSize;
      }
    }

    /// <summary>
    /// Marshals a list of TLVs into a byte array at the beginning of the array
    /// </summary>
    /// <param name="tlvs">A list of populated TLV structures</param>
    /// <param name="buffer">A byte array</param>
    public static void InsertTLVs(TLV[] tlvs, byte[] buffer)
    {
      int index = 0;
      InsertTLVs(tlvs, buffer, ref index);
    }

    /// <summary>
    /// Unmarshals a byte array into a UserInfo block
    /// </summary>
    /// <param name="buffer">A byte array</param>
    /// <param name="index">The position of the UserInfo block within the array</param>
    /// <returns>A populated UserInfo block</returns>
    public static UserInfo GetUserinfoBlock(byte[] buffer, ref int index)
    {
      bool valid = false;
      return GetUserinfoBlock(buffer, ref index, ref valid);
    }

    /// <summary>
    /// Unmarshals a byte array into a UserInfo block
    /// </summary>
    /// <param name="buffer">A byte array</param>
    /// <param name="index">The position of the UserInfo block within the array</param>
    /// <param name="valid">A <c>bool</c> indicating whether the returned UserInfo block is valid</param>
    /// <returns>A populated UserInfo block</returns>
    public static UserInfo GetUserinfoBlock(byte[] buffer, ref int index, ref bool valid)
    {
      UserInfo retval = new UserInfo();
      byte sn_length = buffer[index++];
      if (sn_length == 0)
      {
        valid = false;
        return retval;
      }

      retval.ScreenName = Marshal.ByteArrayToString(buffer, ref index, sn_length, Marshal.ASCII);
      retval.WarningLevel = Marshal.ByteArrayToUshort(buffer, ref index);
      retval.Icon = null;

      ushort tlv_number = Marshal.ByteArrayToUshort(buffer, ref index);

      for (int i = 0; i < tlv_number; i++)
      {
        ushort key = Marshal.ByteArrayToUshort(buffer, ref index);
        ushort keylength = Marshal.ByteArrayToUshort(buffer, ref index);
        switch (key)
        {
          case 0x0001:
            retval.Class = (UserClass)Marshal.ByteArrayToUshort(buffer, ref index);
            break;
          case 0x0002:
            retval.CreateTime = Marshal.ByteArrayToUint(buffer, ref index);
            break;
          case 0x0003:
            retval.SignonTime = Marshal.ByteArrayToUint(buffer, ref index);
            break;
          case 0x0004:
            retval.IdleTime = Marshal.ByteArrayToUshort(buffer, ref index);
            break;
          case 0x0005:
            retval.RegisterTime = Marshal.ByteArrayToUint(buffer, ref index);
            break;
          case 0x0006:
            retval.ICQUserStatus = Marshal.ByteArrayToUint(buffer, ref index);
            break;
          case 0x000A:
            retval.ExternalIPAddress = Marshal.ByteArrayToUint(buffer, ref index);
            break;
          case 0x000C:
            retval.DC = Marshal.GetDCInfo(buffer, ref index);
            break;
          case 0x000D:
            byte[] caps = new byte[keylength];
            Marshal.CopyArray(buffer, caps, ref index);
            retval.ClientCapabilities = CapabilityProcessor.ProcessCLSIDList(caps);
            break;
          case 0x000F:
            retval.OnlineTime = Marshal.ByteArrayToUint(buffer, ref index);
            break;
          case 0x001D:
            retval.Icon = new IconInfo();
            // According to libfaim, this here is some pretty crazy crap
            // that iChat engineers are responsible for. I don't know about that,
            // but it is pretty nuts
            int end = index + keylength;
            while (index < end)
            {
              ushort type = Marshal.ByteArrayToUshort(buffer, ref index);
              byte number = buffer[index++];
              byte length = buffer[index++];
              switch (type)
              {
                case 0x0000:	// Same five bytes all the time
                  index += length;
                  break;
                case 0x0001:	// Icon checksum
                  if (length > 0) // libfaim has a check here for number == 0 or 1
                  {
                    retval.Icon.ID = 0x0001;
                    retval.Icon.HashType = number;
                    retval.Icon.Hash = new byte[length];
                    Marshal.CopyArray(buffer, retval.Icon.Hash, ref index);
                  }
                  else
                    index += length;
                  break;
                case 0x0002:	// Available message? Thanks iChat
                  if (length > 4)
                  {
                    // Pull the message to a byte array to get the encoding
                    // before conversion to a string
                    byte[] amessage = new byte[Marshal.ByteArrayToUshort(buffer, ref index)];
                    Marshal.CopyArray(buffer, amessage, ref index);

                    // Check to see if the encoding's been specified, otherwise use
                    // us-ascii as a default
                    string aencoding = "us-ascii";
                    if (Marshal.ByteArrayToUshort(buffer, ref index) == 0x0001)
                    {
                      // Some two bytes for no reason
                      Marshal.ByteArrayToUshort(buffer, ref index);

                      aencoding = Marshal.ByteArrayToString(
                        buffer, ref index,
                        Marshal.ByteArrayToUshort(buffer, ref index), Marshal.ASCII);
                    }

                    // Set the encoding and the decoded message
                    retval.AvailableMessageEncoding = Encoding.GetEncoding(aencoding);
                    retval.AvailableMessage = retval.AvailableMessageEncoding.GetString(amessage, 0, amessage.Length);
                  }
                  else
                    index += length;
                  break;
                default:
                  index += length;
                  break;
              }
            }
            if (retval.Icon.Hash == null)
            {
              retval.Icon = null;
            }
            break;
          default:
            index += keylength;
            break;
        }
      }

      valid = true;
      return retval;
    }

    private static UserDCInfo GetDCInfo(byte[] buffer, ref int index)
    {
      UserDCInfo retval = new UserDCInfo();
      retval.InternalIP = Marshal.UIntToIPAddress(Marshal.ByteArrayToUint(buffer, ref index));
      retval.InternalPort = Marshal.ByteArrayToUint(buffer, ref index);
      retval.DCType = buffer[index++];
      retval.DCVersion = Marshal.ByteArrayToUshort(buffer, ref index);
      retval.AuthCookie = Marshal.ByteArrayToUint(buffer, ref index);
      retval.WebFrontPort = Marshal.ByteArrayToUint(buffer, ref index);
      retval.ClientFutures = Marshal.ByteArrayToUint(buffer, ref index);
      retval.LastInfoUpdateTime = Marshal.ByteArrayToUint(buffer, ref index);
      retval.LastExtInfoUpdateTime = Marshal.ByteArrayToUint(buffer, ref index);
      retval.LastExtStatusUpdateTime = Marshal.ByteArrayToUint(buffer, ref index);
      retval.Unknown = Marshal.ByteArrayToUshort(buffer, ref index);
      return retval;
    }

    public static byte[] SSIItemsToByteArray(SSIItem[] items)
    {
      // Allocate a buffer
      int buffersize = 0;
      int tlvsize = 0;
      for (int i = 0; i < items.Length; i++)
      {
        buffersize += 10 + items[i].Name.Length;
        TLV[] tlvs = items[i].Tlvs;
        int k = (tlvs == null) ? 0 : tlvs.Length;
        for (int j = 0; j < k; j++)
        {
          buffersize += 4 + tlvs[j].DataSize;
          tlvsize += 4 + tlvs[j].DataSize;
        }
      }
      byte[] retval = new byte[buffersize];

      // Write the data into the newly allocated buffer
      int index = 0;
      for (int i = 0; i < items.Length; i++)
      {
        Marshal.InsertSSIItem(retval, items[i], (ushort)tlvsize, ref index);
        Marshal.InsertTLVs(items[i].Tlvs, retval, ref index);
      }

      return retval;
    }

    public static void InsertSSIItem(byte[] buffer, SSIItem si, ushort tlvsize, ref int index)
    {
      Marshal.InsertUshort(buffer, (ushort)si.Name.Length, ref index);
      Marshal.InsertString(buffer, si.Name, Marshal.ASCII, ref index);
      Marshal.InsertUshort(buffer, si.GroupID, ref index);
      Marshal.InsertUshort(buffer, si.ItemID, ref index);
      Marshal.InsertUshort(buffer, si.ItemType, ref index);
      Marshal.InsertUshort(buffer, tlvsize, ref index);
    }

    /// <summary>
    /// Marshals a string into a ASCII byte array
    /// </summary>
    /// <param name="s">A string</param>
    /// <returns>A byte array containing the ASCII representation of the string</returns>
    public static byte[] StringToByteArray(string s, Encoding encoding)
    {
      return encoding.GetBytes(s);
    }

    /// <summary>
    /// Marshals a four-byte unsigned integer into a byte array
    /// </summary>
    /// <param name="i">An unsigned integer</param>
    /// <returns>A byte array containing the four bytes of the integer in big-endian format</returns>
    public static byte[] UIntToByteArray(uint i)
    {
      byte[] retval = new byte[4];
      retval[0] = (byte)((i & 0xFF000000) >> 24);
      retval[1] = (byte)((i & 0x00FF0000) >> 16);
      retval[2] = (byte)((i & 0x0000FF00) >> 8);
      retval[3] = (byte)(i & 0x000000FF);
      return retval;
    }

    public static string UIntToIPAddress(uint i)
    {
      byte[] bytes = Marshal.UIntToByteArray(i);
      return String.Format("{0}.{1}.{2}.{3}", bytes[0], bytes[1], bytes[2], bytes[3]);
    }

    /// <summary>
    /// Marshals a two-byte unsigned short into a byte array
    /// </summary>
    /// <param name="s">An unsigned short</param>
    /// <returns>A byte array containing the two bytes of the short in big-endian format</returns>
    public static byte[] UShortToByteArray(ushort s)
    {
      byte[] retval = new byte[2];
      retval[0] = (byte)((s & 0xFF00) >> 8);
      retval[1] = (byte)(s & 0x00FF);
      return retval;
    }

    /// <summary>
    /// Converts two bytes of a byte array to an unsigned short and updates the index parameter
    /// </summary>
    /// <param name="buffer">A byte array</param>
    /// <param name="index">A reference to the index where the unsigned short is located</param>
    /// <returns>An unsigned short consisting of two bytes in the array, aligned MSB</returns>
    /// <remarks>This method updates the index parameter to the entry just after
    /// the unsigned short in the array.</remarks>
    public static ushort ByteArrayToUshort(byte[] buffer, ref int index)
    {
      ushort retval = (ushort)((buffer[index] << 8) | buffer[index + 1]);
      index += 2;
      return retval;
    }

    /// <summary>
    /// Converts two bytes of a byte array to an unsigned short
    /// </summary>
    /// <param name="buffer">A byte array</param>
    /// <param name="index">The index where the unsigned short is located</param>
    /// <returns>An unsigned short consisting of two bytes in the array, aligned MSB</returns>
    /// <remarks>This method does not change the index parameter.</remarks>
    public static ushort ByteArrayToUshort(byte[] buffer, int index)
    {
      return ByteArrayToUshort(buffer, ref index);
    }

    /// <summary>
    /// Converts four bytes of a byte array to an unsigned integer and updates the index parameter
    /// </summary>
    /// <param name="buffer">A byte array</param>
    /// <param name="index">A reference to the index where the unsigned integer is located</param>
    /// <returns>An unsigned integer consisting of four bytes in the array, aligned MSB</returns>
    /// <remarks>This method updates the index parameter to the entry just after
    /// the unsigned short in the array.</remarks>
    public static uint ByteArrayToUint(byte[] buffer, ref int index)
    {
      uint retval = (uint)((buffer[index] << 24) |
        (buffer[index + 1] << 16) |
        (buffer[index + 2] << 8) |
        buffer[index + 3]);
      index += 4;
      return retval;
    }

    /// <summary>
    /// Converts four bytes of a byte array to an unsigned integer
    /// </summary>
    /// <param name="buffer">A byte array</param>
    /// <param name="index">The index where the unsigned integer is located</param>
    /// <returns>An unsigned short consisting of four bytes in the array, aligned MSB</returns>
    /// <remarks>This method does not change the index parameter.</remarks>
    public static uint ByteArrayToUint(byte[] buffer, int index)
    {
      return ByteArrayToUint(buffer, ref index);
    }

    /// <summary>
    /// Converts a portion of a byte array to a string
    /// </summary>
    /// <param name="buffer">A byte array</param>
    /// <param name="index">The index where the string begins</param>
    /// <param name="length">The length of the string to convert</param>
    /// <param name="encoding">The <see cref="System.Text.Encoding"/> of the contained string</param>
    /// <returns>A string consisting of <cparam>length</cparam> bytes out of the array with the given encoding</returns>
    /// <exception cref="ArgumentException">Throws when index + length is greater than the size of the buffer</exception>
    public static string ByteArrayToString(byte[] buffer, ref int index, int length, Encoding encoding)
    {
      if (index + length > buffer.Length)
        throw new ArgumentException("index + length overflows buffer");

      string retval = encoding.GetString(buffer, index, length);
      index += length;
      return retval;
    }

    public static string ByteArrayToNullTerminatedString(byte[] buffer, ref int index, int length)
    {
      StringBuilder retval = new StringBuilder();
      for (int i = index; i < index + length; i++)
      {
        // Friggin' file transfers and their "null padded strings"
        if (buffer[i] != 0x00)
          retval.Append((char)buffer[i]);
      }
      index += length;
      return retval.ToString();
    }

    /// <summary>
    /// Converts a portion of a byte array to a string
    /// </summary>
    /// <param name="buffer">A byte array</param>
    /// <param name="index">The index where the string begins</param>
    /// <param name="length">The length of the string to convert</param>
    /// <param name="encoding">The <see cref="System.Text.Encoding"/> of the contained string</param>
    /// <returns>A string consisting of <cparam>length</cparam> bytes out of the array with the given encoding</returns>
    /// <exception cref="ArgumentException">Throws when index + length is greater than the size of the buffer</exception>
    /// <remarks>This method does not change the index parameter.</remarks>
    public static string ByteArrayToString(byte[] buffer, int index, int length, Encoding encoding)
    {
      return ByteArrayToString(buffer, ref index, length, encoding);
    }

    /// <summary>
    /// Writes an unsigned short (two bytes) into an array
    /// </summary>
    /// <param name="buffer">A byte buffer</param>
    /// <param name="u">An unsigned short</param>
    /// <param name="index">The index at which to write the two bytes</param>
    public static void InsertUshort(byte[] buffer, ushort u, int index)
    {
      InsertUshort(buffer, u, ref index);
    }

    /// <summary>
    /// Writes an unsigned short (two bytes) into an array
    /// </summary>
    /// <param name="buffer">A byte buffer</param>
    /// <param name="u">An unsigned short</param>
    /// <param name="index">The index at which to write the two bytes</param>
    public static void InsertUshort(byte[] buffer, ushort u, ref int index)
    {
      if (index + 2 > buffer.Length)
      {
        // trouble
      }
      buffer[index++] = (byte)((u & 0xFF00) >> 8);
      buffer[index++] = (byte)(u & 0x00FF);
    }

    /// <summary>
    /// Writes an unsigned int (four bytes) into an array
    /// </summary>
    /// <param name="buffer">A byte buffer</param>
    /// <param name="u">An unsigned int</param>
    /// <param name="index">The index at which to write the four bytes</param>
    public static void InsertUint(byte[] buffer, uint u, ref int index)
    {
      if (index + 4 > buffer.Length)
      {
        // trouble
      }
      buffer[index++] = (byte)((u & 0xFF000000) >> 24);
      buffer[index++] = (byte)((u & 0x00FF0000) >> 16);
      buffer[index++] = (byte)((u & 0x0000FF00) >> 8);
      buffer[index++] = (byte)(u & 0x000000FF);
    }

    /// <summary>
    /// Writes an string into an array
    /// </summary>
    /// <param name="buffer">A byte buffer</param>
    /// <param name="s">A string</param>
    /// <param name="index">The index at which to write the string</param>
    public static void InsertString(byte[] buffer, string s, Encoding encoding, ref int index)
    {
      byte[] bytes = encoding.GetBytes(s);
      if (index + bytes.Length > buffer.Length)
      {
      }

      Marshal.CopyArray(bytes, buffer, 0, ref index);

      /*byte[] str = Marshal.
        StringToByteArray(s);
      for (int i = 0; i < str.Length; i++)
        buffer[index++] = str[i];//*/
    }

    /// <summary>
    /// Unmarshals a byte array into an SSI item
    /// </summary>
    /// <param name="buffer">A byte array</param>
    /// <param name="index">The position of the SSI item within the array</param>
    /// <returns>A populated <see cref="SSIItem"/> object</returns>
    public static SSIItem GetSSIItem(byte[] buffer, ref int index)
    {
      SSIItem item = new SSIItem();
      ushort item_name_length = Marshal.ByteArrayToUshort(buffer, ref index);
      item.Name = Marshal.ByteArrayToString(buffer, ref index, item_name_length, Marshal.ASCII);
      item.GroupID = Marshal.ByteArrayToUshort(buffer, ref index);
      item.ItemID = Marshal.ByteArrayToUshort(buffer, ref index);
      item.ItemType = Marshal.ByteArrayToUshort(buffer, ref index);

      ushort data_length = Marshal.ByteArrayToUshort(buffer, ref index);
      int data_end = index + data_length;
      System.Collections.ArrayList tlvlist = new System.Collections.ArrayList();
      while (index < data_end)
      {
        TLV tlv = new TLV();
        tlv.TypeNumber = Marshal.ByteArrayToUshort(buffer, ref index);
        tlv.DataSize = Marshal.ByteArrayToUshort(buffer, ref index);
        tlv.Data = new byte[tlv.DataSize];
        Marshal.CopyArray(buffer, tlv.Data, ref index);
        tlvlist.Add(tlv);
      }

      TLV[] tlvs = new TLV[tlvlist.Count];
      for (int i = 0; i < tlvs.Length; i++)
        tlvs[i] = (TLV)tlvlist[i];
      item.Tlvs = tlvs;

      return item;
    }

    /// <summary>
    /// Unmarshals a byte array into an interest item
    /// </summary>
    /// <param name="buffer">A byte array</param>
    /// <param name="index">The position of the interest item within the array</param>
    /// <returns>A populated <see cref="InterestItem"/> object</returns>
    public static InterestItem GetInterestItem(byte[] buffer, ref int index)
    {
      InterestItem retval = new InterestItem();
      retval.Group = (buffer[index++] == 0x01);
      retval.ID = buffer[index++];
      ushort strlen = Marshal.ByteArrayToUshort(buffer, ref index);
      retval.Name = Marshal.ByteArrayToString(buffer, ref index, strlen, Marshal.ASCII);
      return retval;
    }

    /// <summary>
    /// Extracts SNAC data from a received buffer
    /// </summary>
    /// <param name="buffer">The buffer containing the SNAC data</param>
    /// <param name="header">An out parameter to return the SNAC header associated with this data</param>
    /// <returns>A byte array containing SNAC data</returns>
    public static byte[] GetSNACData(byte[] buffer, out SNACHeader header)
    {
      int index = 10;

      header = Marshal.GetSNACHeader(buffer);
      if ((header.Flags & 0x8000) != 0)
      {
        // Apparently, this is where we very kindly receive the SNAC family version
        // information.  I appreciate that AOL furnishes this information, but it was
        // deadlocking OscarLib and that makes me MAD >:(
        int distance = Marshal.ByteArrayToUshort(buffer, ref index);
        index += distance;

        // Log what got removed
        byte[] tmp = new byte[index - 10];
        Marshal.CopyArray(buffer, tmp, 10);
        //Logging.DumpFLAP(tmp, String.Format("Removed data from SNAC({0:x4},{1:x4}) flagged 0x8000", header.FamilyServiceID, header.FamilySubtypeID));
      }
      byte[] SNACData = new byte[buffer.Length - index];
      Marshal.CopyArray(buffer, SNACData, index);

      return SNACData;
    }

    public static DataPacket BuildDataPacket(Session sess, SNACHeader header, byte[] data)
    {
      DataPacket dp = new DataPacket();
      dp.ParentSession = sess;
      dp.SNAC = header;
      dp.Data = data;
      return dp;
    }

    public static Encoding ASCII
    {
      get { return Encoding.ASCII; }
    }

    public static Encoding Unicode2
    {
      get { return Encoding.BigEndianUnicode; }
    }

    public static ushort EncodingToCharset(Encoding encoding)
    {
      if(encoding == Marshal.ASCII)
      {
        return 0x0000;
      }
      else if(encoding == Marshal.Unicode2)
      {
        return 0x0002;
      }
      return 0x0003;
    }

    public static Encoding AolMimeToEncoding(string aolmime)
    {
      Encoding retval = Encoding.ASCII;

      int charsetindex = aolmime.IndexOf("charset=");
      if (charsetindex != -1)
      {
        aolmime = aolmime.Substring(charsetindex + 8).Trim('\"');
      }

      try
      {
        retval = Encoding.GetEncoding(aolmime);
      }
      catch
      {
        // Gaim (at least) sends unicode-2-0 when it means utf-FFFE on Windows
        if (aolmime.ToLower() == "unicode-2-0")
        {
          retval = Encoding.BigEndianUnicode;
        }
      }

      return retval;
    }

    /// <summary>
    /// Gets a byte array containing the AOL representation of the specified Encoding
    /// </summary>
    /// <param name="e">The <see cref="System.Text.Encoding"/> to convert</param>
    /// <returns>A byte array containing the US-ASCII string of the encoding</returns>
    /// <remarks>
    /// This function supports:
    ///		<list type="bullet">
    ///   <item><see cref="System.Text.Encoding.ASCII"/> -- text/aolrtf; charset="us-ascii"</item>
    ///		<item><see cref="System.Text.Encoding.BigEndianUnicode"/> -- text/aolrtf; charset="unicode-2-0"</item>
    /// 	<item>ISO-8859-1 (Latin1) -- text/aolrtf; charset="iso-8859-1"</item>
    ///		<item>Other -- text/aolrtf; charset="[webname]", where [webname] is the <see cref="System.Text.Encoding.WebName"/>
    ///         of the argument</item>
    ///		</list>
    /// </remarks>
    public static string EncodingToAolMime(Encoding e)
    {
      if (e == Encoding.BigEndianUnicode)
      {
        return "text/aolrtf; charset=\"unicode-2-0\"";
      }


	  // TODO: Compact Framework probably needs to handle this encoding somehow... . detect a different way?
#if !WindowsCE
      if (e == Encoding.GetEncoding("iso-8859-1"))
      {
        return "text/aolrtf; charset=\"iso-8859-1\"";
      }
#endif

      return "text/aolrtf; charset=\"" + e.WebName + "\"";
    }

    public static byte[] ConvertStringEncodings(byte[] target, Encoding from, Encoding to)
    {
      return to.GetBytes(from.GetString(target, 0, target.Length));
    }

    #region CopyArray and overloads
    public static void CopyArray(byte[] src, byte[] dest, int src_start)
    {
      CopyArray(src, dest, ref src_start);
    }

    public static void CopyArray(byte[] src, byte[] dest, ref int src_start)
    {
      int dest_start = 0;
      CopyArray(src, dest, ref src_start, ref dest_start);
    }

    public static void CopyArray(byte[] src, byte[] dest, int src_start, int dest_start)
    {
      CopyArray(src, dest, ref src_start, ref dest_start);
    }

    public static void CopyArray(byte[] src, byte[] dest, int src_start, ref int dest_start)
    {
      CopyArray(src, dest, ref src_start, ref dest_start);
    }

    public static void CopyArray(byte[] src, byte[] dest, ref int src_start, ref int dest_start)
    {
      int j = src.Length;
      int k = dest.Length;
      while (src_start < j && dest_start < k)
      {
        dest[dest_start++] = src[src_start++];
      }
    }
    #endregion

    #region File transfer inserts

    public static void InsertFileHeader(byte[] buffer, FileTransferConnection conn, ref int index)
    {
      FileHeader fh = conn.FileHeader;

      int i;
      byte[] idstring = new byte[32];
      byte[] dummy = new byte[69];
      byte[] macinfo = new byte[16];
      byte[] filename = new byte[64];

      for (i = 0; i < 32 && i < fh.IdString.Length; i++)
      {
        idstring[i] = (byte)fh.IdString[i];
      }
      idstring[31] = 0x00;
      while (i < 32)
      {
        idstring[i++] = 0x00;
      }

      for (i = 0; i < 69; i++)
        dummy[i] = 0x00;

      for (i = 0; i < 16; i++)
        macinfo[i] = 0x00;

      for (i = 0; i < 64 && i < fh.Name.Length; i++)
      {
        filename[i] = (byte)fh.Name[i];
      }
      filename[63] = 0x00;
      while (i < 64)
      {
        filename[i++] = 0x00;
      }

      // 8
      CopyArray(fh.Cookie, buffer, 0, ref index);
      // 16
      InsertUshort(buffer, fh.Encryption, ref index);
      InsertUshort(buffer, fh.Compression, ref index);
      InsertUshort(buffer, (ushort)conn.TotalFiles, ref index);
      InsertUshort(buffer, (ushort)conn.FilesRemaining, ref index);
      InsertUshort(buffer, (ushort)conn.TotalParts, ref index);
      InsertUshort(buffer, fh.PartsLeft, ref index);
      // 28
      InsertUint(buffer, conn.TotalFileSize, ref index);
      InsertUint(buffer, fh.Size, ref index);
      InsertUint(buffer, fh.modtime, ref index);
      InsertUint(buffer, fh.Checksum, ref index);
      InsertUint(buffer, fh.ResourceForkReceivedChecksum, ref index);
      InsertUint(buffer, fh.ResourceForkSize, ref index);
      InsertUint(buffer, fh.cretime, ref index);
      InsertUint(buffer, fh.ResourceForkChecksum, ref index);
      InsertUint(buffer, fh.nrecvd, ref index);
      InsertUint(buffer, fh.ReceivedChecksum, ref index);
      // 68
      CopyArray(idstring, buffer, 0, ref index);
      // 100
      buffer[index++] = fh.flags;
      buffer[index++] = fh.lnameoffset;
      buffer[index++] = fh.lsizeoffset;
      // 103
      CopyArray(dummy, buffer, 0, ref index);
      // 172
      CopyArray(macinfo, buffer, 0, ref index);
      // 188
      InsertUshort(buffer, fh.nencode, ref index);
      InsertUshort(buffer, fh.nlanguage, ref index);
      // 192
      CopyArray(filename, buffer, 0, ref index);
      // 256
    }

    public static void ByteArrayToFTI(byte[] buffer, ref int index, FileTransferConnection conn)
    {
      FileHeader fh = conn.FileHeader;

      CopyArray(buffer, fh.Cookie, ref index);

      fh.Encryption = ByteArrayToUshort(buffer, ref index);
      fh.Compression = ByteArrayToUshort(buffer, ref index);
      conn.TotalFiles = ByteArrayToUshort(buffer, ref index);
      conn.FilesRemaining = ByteArrayToUshort(buffer, ref index);
      conn.TotalParts = ByteArrayToUshort(buffer, ref index);
      fh.PartsLeft = ByteArrayToUshort(buffer, ref index);

      conn.TotalFileSize = ByteArrayToUint(buffer, ref index);
      fh.Size = ByteArrayToUint(buffer, ref index);
      fh.modtime = ByteArrayToUint(buffer, ref index);
      fh.Checksum = ByteArrayToUint(buffer, ref index);
      fh.ResourceForkReceivedChecksum = ByteArrayToUint(buffer, ref index);
      fh.ResourceForkSize = ByteArrayToUint(buffer, ref index);
      fh.cretime = ByteArrayToUint(buffer, ref index);
      fh.ResourceForkChecksum = ByteArrayToUint(buffer, ref index);
      fh.nrecvd = ByteArrayToUint(buffer, ref index);
      fh.ReceivedChecksum = ByteArrayToUint(buffer, ref index);

      fh.IdString = ByteArrayToNullTerminatedString(buffer, ref index, 32);

      fh.flags = buffer[index++];
      fh.lnameoffset = buffer[index++];
      fh.lsizeoffset = buffer[index++];

      CopyArray(buffer, fh.dummy, ref index);

      CopyArray(buffer, fh.macfileinfo, ref index);

      fh.nencode = ByteArrayToUshort(buffer, ref index);
      fh.nlanguage = ByteArrayToUshort(buffer, ref index);

      fh.Name = ByteArrayToNullTerminatedString(buffer, ref index, 64);
    }

    public static byte[] CreateFileTransferMessage(ushort type, FileTransferConnection conn)
    {
      int index = 0;
      byte[] retval = new byte[256];
      InsertString(retval, "OFT2", Marshal.ASCII, ref index);
      InsertUshort(retval, 0x0100, ref index);
      InsertUshort(retval, type, ref index);
      InsertFileHeader(retval, conn, ref index);

      return retval;
    }
    #endregion
  }
}
